package com.df.ez_game.server.req_metadata;

import com.baidu.bjf.remoting.protobuf.Codec;
import com.baidu.bjf.remoting.protobuf.ProtobufProxy;
import com.baidu.bjf.remoting.protobuf.annotation.ProtobufClass;
import com.df.ez_game.business.annotation.EzController;
import com.df.ez_game.business.annotation.EzMapping;
import com.df.ez_game.server.MsgProtocType;
import com.df.ez_game.server.msg.client.ClientReqMsg;
import com.df.ez_game.server.msg.client.ClientRespMsg;
import com.df.ez_game.server.msg.wrapper.*;
import com.df.ez_game.tools.MsgWrapperUtils;
import com.esotericsoftware.reflectasm.MethodAccess;
import lombok.extern.slf4j.Slf4j;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
public class ControllerMetadatas {
    //TODO 换成数组映射
    //{controllerId:唯一controllerId对应的数据}
    private  ConcurrentHashMap<Integer, EzControllerData> datas = new ConcurrentHashMap<Integer, EzControllerData>();
    //===============================映射数据=============================================
    //路径id和处理方法的映射
    private  HashMap<Integer, EzMethodData> methodsCache = new HashMap<>();

    private  EzMethodData[][] methodDatas;

    //===============================映射数据=============================================

    private Set<Class> protoClasses =null;
    private Set<Class> ezProtoClass =null;
    private Integer msgProtoType = null;

    public void setMsgProtoType(Integer msgProtoType){
        this.msgProtoType = msgProtoType;
    }

    public  ConcurrentHashMap<Integer, EzControllerData> getDatas(){
        return datas;
    }

    public Set<Class> protoClasses(){
        return protoClasses;
    }

    public  EzMethodData getLogicMethodData(Integer methodFullId){
        EzMethodData data = methodsCache.get(methodFullId);
        return data;
    }

    public void parserControllers(Set<Object> ctrollerObjs){
        for (Object ctrollerObj : ctrollerObjs) {
            parseControllerObj(null,ctrollerObj,true);
        }
    }
    public void parserGeneratorControllers(Set<Class<?>> ctrollerObjs){
        for (Class clazz : ctrollerObjs) {
            parseControllerObj(clazz,null,false);
        }
    }

    //TODO 编译的时候就直接解析？
    public  void parseControllerObj(Class ctlClazz,Object ctlObj,boolean runTime){

        if(runTime){
            if(ctlObj==null){
                return;
            }
            ctlClazz = ctlObj.getClass();
        }else {
            ezProtoClass =   new HashSet<>();
            ezProtoClass.add(ClientReqMsg.class);
            ezProtoClass.add(ClientRespMsg.class);
            ezProtoClass.add(IntWrapper.class);
            ezProtoClass.add(IntListWrapper.class);
            ezProtoClass.add(LongWrapper.class);
            ezProtoClass.add(LongListWrapper.class);
            ezProtoClass.add(BoolWrapper.class);
            ezProtoClass.add(BoolListWrapper.class);
            ezProtoClass.add(StringWrapper.class);
            ezProtoClass.add(StringListWrapper.class);
            ezProtoClass.add(ObjectListWrapper.class);

            protoClasses = new HashSet<>();
        }
        if(ctlClazz==null){
            return;
        }

        EzController annotation = (EzController) ctlClazz.getAnnotation(EzController.class);
        if(annotation==null){
            return;
        }

        String name = annotation.name();
        if(name.trim().equals("")){
            name = ctlClazz.getSimpleName();
        }
        String desc = annotation.desc();
        int ctlId = annotation.id();

        //{name:数据}
        EzControllerData controllerData = datas.getOrDefault(ctlId,new EzControllerData());
        datas.put(ctlId, controllerData);
        controllerData.setControllerName(name);
        controllerData.setControllerClassName(ctlClazz.getName());
        controllerData.setControllerClassType(ctlClazz);
        controllerData.setControllerId(ctlId);
        controllerData.setControllerDesc(desc);

        Method[] methods = ctlClazz.getMethods();
        //消息种类：
        //1.客户端请求并返回
        //定位哪个类的哪个方法，用完成的路径计算的id进行映射
        //方法的参数使用参数类名计算的唯一id和类进行映射
        //方法的返回值使用类名计算的唯一id和类进行映射
        //2.客户端请求不返回
        //3.服务器给客户端推送
        MethodAccess methodAccess = null;
        if(runTime){
            methodAccess = MethodAccess.get(ctlClazz);
        }

        for (Method method : methods) {
            EzMapping methodMapping = method.getAnnotation(EzMapping.class);
            if(methodMapping==null){
                continue;
            }
            String methodName = methodMapping.name();
            if(methodName.trim().equals("")){
                methodName = method.getName();
            }

            String fullPath = name +"/"+ methodName;
            //计算消息id
            int methodId = methodMapping.id();
            //参数
            int parameterCount = method.getParameterCount();
            if(parameterCount>1){
                log.error("消息处理方法参数超过1个！路径："+fullPath);
                continue;
            }
            //高低个占用16位
            Integer methodFullId = (ctlId&0xffff)<<16|(methodId&0xffff);
            EzMethodData methodDataCache = methodsCache.get(methodFullId);
            if(methodDataCache!=null){
                throw new RuntimeException("logic method 重复，重复方法id："+methodFullId);
            }
            //TODO 父路由和子路由使用二维数组进行存储，访问时效率高，map获取时会进行hash运算
            EzMethodData methodData = controllerData.getMethos().getOrDefault(methodFullId, new EzMethodData());


            methodData.setMethodId(methodId);
            methodData.setMethodFullId(methodFullId);
            methodData.setMethodName(methodName);
            methodData.setMethodDesc(methodMapping.desc());


            Class paramType = null;
            //TODO 带泛型的对象list检查泛型类型是不是基本类型或protobuff修饰的类
            if(parameterCount==1){
                Parameter parameter = method.getParameters()[0];
                if(this.msgProtoType== MsgProtocType.PROTOBUF.getValue()){
                    ProtobufClass paramAnno = parameter.getType().getAnnotation(ProtobufClass.class);
                    if(paramAnno==null){
                        //判定是不是基本类型
                        if(!MsgWrapperUtils.isSupportWrapperType(parameter.getType())){
                            log.error("消息处理方法只能接收被Protobuf注解的消息或基本类型！路径："+fullPath);
                            continue;
                        }else {
                            methodData.setParamIsBaseType(true);
                        }
                    }
                }
//                else {
//                    //判定是不是基本类型
//                    if(!MsgWrapperUtils.isSupportWrapperType(parameter.getType())){
//                        log.error("消息处理方法只能接收基本类型或对象类型！路径："+fullPath);
//                        continue;
//                    }else {
//                        methodData.setParamIsBaseType(true);
//                    }
//                }

                paramType = parameter.getType();
                if(!runTime){
                    protoClasses.add(paramType);
                }
                methodData.setParamType(paramType);
                methodData.setParamName(parameter.getName());
            }


            //返回值
            Class returnType = method.getReturnType();
            Type genericReturnType = method.getGenericReturnType();
            if(returnType!=null){
                if(this.msgProtoType==MsgProtocType.PROTOBUF.getValue()){
                    Annotation returnAnno = returnType.getAnnotation(ProtobufClass.class);
                    if(returnAnno==null){
                        if(!MsgWrapperUtils.isSupportWrapperType(returnType)){
                            log.error("消息处理方法返回类型必须被Protobuf注解！路径："+fullPath);
                        }else {
                            methodData.setReturnIsBaseType(true);
                        }
                        continue;
                    }
                }

                methodData.setReturnType(returnType);
            }


            //id和路径映射



            if(runTime){
                int methodIndex = methodAccess.getIndex(method.getName(),method.getParameterTypes());
                methodData.methodIndex = methodIndex;
                methodData.instance = ctlObj;
            }
            methodData.methodAccess = methodAccess;



            if(paramType!=null){
                if(methodData.isParamIsBaseType()){
                    //TODO 包装类型的编码器
                    Class wrapperType = createWrapperTypeCodec(paramType);
                    if(wrapperType==null){
                        throw new RuntimeException("未支持的基本类型："+paramType.getName());
                    }
                    if(this.msgProtoType==MsgProtocType.PROTOBUF.getValue()){
                        ProtobufProxy.create(wrapperType);
                    }

                    methodData.setParamWrapperType(wrapperType);
//                    methodData.setParamCodec(codec);
                }else {
                    if(this.msgProtoType==MsgProtocType.PROTOBUF.getValue()){
                        ProtobufProxy.create(paramType);
                    }
//                    methodData.setParamCodec(codec);
                }
            }
            if(returnType!=null){
                if(methodData.isReturnIsBaseType()){
                    //TODO 包装类型的编码器
                    Class wrapperType = createWrapperTypeCodec(paramType);
                    if(wrapperType==null){
                        throw new RuntimeException("未支持的基本类型："+paramType.getName());
                    }
                    if(this.msgProtoType==MsgProtocType.PROTOBUF.getValue()){
                        ProtobufProxy.create(wrapperType);
                    }
//                    Codec codec = ProtobufProxy.create(wrapperType);
                    methodData.setReturnWrapperType(wrapperType);
//                    methodData.setReturnCodec(codec);
                }else {
                    if(this.msgProtoType==MsgProtocType.PROTOBUF.getValue()){
                        ProtobufProxy.create(returnType);
                    }
//                    Codec codec = ProtobufProxy.create(returnType);
//                    methodData.setReturnCodec(codec);
                }
            }
            controllerData.getMethos().put(methodFullId,methodData);
            methodsCache.put(methodFullId,methodData);
            System.out.println("cmd:"+controllerData.getControllerId()+"_"+methodData.getMethodId()+" name:"+controllerData.getControllerName()+"."+methodName);
        }

    }



    private static Class createWrapperTypeCodec(Class paramType) {
        // byte short int long list map
//        if(paramType.equals(Byte.TYPE)){
//            return ByteWrapper.class;
//        }
//        if(paramType.equals(Short.TYPE)){
//            return ShortWrapper.class;
//        }
        if(paramType.equals(Integer.TYPE)){
            return IntWrapper.class;
        }
        if(paramType.equals(Integer.class)){
            return IntWrapper.class;
        }
//        if(paramType.equals(Long.TYPE)){
//            return LongWrapper.class;
//        }
//        if(paramType.equals(Byte.TYPE)){
//            return ByteWrapper.class;
//        }
//        if(paramType.equals(Byte.TYPE)){
//            return ByteWrapper.class;
//        }
        throw new RuntimeException("未支持的基本类型："+paramType.getName());
    }

    private static int getmsgId(String fullPath, Set<Integer> integers) {
        char[] charArray = fullPath.toCharArray();
        int h = 0;
        for (char c :charArray){
            h = 31 * h + c;
        }
        h = h & 0x0000ffff;
        while (integers.contains(h)) {
            h++;
            h = h & 0x0000ffff;
        }
        return h;
    }
}
